/**********************************************************************
 *** *                                                            * ***
 *** *  Copyright (c) 2013 Harmonia Holdings Group LLC            * ***
 *** *                                                            * ***
 *** * Permission is hereby granted to use, modify, copy, and     * ***
 *** * distribute this software and its documentation for any     * ***
 *** * purpose only without profit, provided that the above       * ***
 *** * Copyright Notice appears in all copies and that both the   * ***
 *** * Copyright Notice and this Permission Notice appears in     * ***
 *** * every copy of supporting documentation.  No title to nor   * ***
 *** * ownership of the software is transferred hereby.  The name * ***
 *** * of Harmonia Holdings Group LLC shall not be used in        * ***
 *** * advertising or publicity related to the distribution of    * ***
 *** * the software without specific, written, prior permission.  * ***
 *** * This software is provided as-delivered without expressed   * ***
 *** * or implied warranty.  Harmonia Holdings Group LLC          * ***
 *** * makes no representation about the suitability of this      * ***
 *** * software for any purpose and accepts no responsibility for * ***
 *** * its use.                                                   * ***
 *** *                                                            * ***
 *** ************************************************************** ***
 *** *                                                            * ***
 *** * This program is free software; you can redistribute it     * ***
 *** * and/or modify it under the terms of the GNU General Public * ***
 *** * License as published by the Free Software Foundation;      * ***
 *** * either version 2 of the License, or (at your option) any   * ***
 *** * later version.                                             * ***
 *** *                                                            * ***
 *** * This program is distributed in the hope that it will be    * ***
 *** * useful, but WITHOUT ANY WARRANTY; without even the implied * ***
 *** * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    * ***
 *** * PURPOSE.  See the GNU General Public License for more      * ***
 *** * details.                                                   * ***
 *** *                                                            * ***
 *** * You should have received a copy of the GNU General Public  * ***
 *** * License along with this program; if not, write to the Free * ***
 *** * Software Foundation, Inc., 51 Franklin Street, Fifth       * ***
 *** * Floor, Boston, MA 02110-1301, USA.                         * ***
 *** *                                                            * ***
 *** * For more information: http://www.gnu.org/licenses/gpl.html * ***
 *** *                                                            * ***
 **********************************************************************/

package org.etexascode.interrep.datamodel;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import org.etexascode.interrep.datamodel.utils.UtilsStringOnModel;

/**
 * Manages all of the vehicles in the intersection
 *
 * @author egaebel
 * @author dranker
 * @author bbadillo
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(value = Vehicle.class)
@XmlRootElement(name="VehicleManager")
public class VehicleManager implements Serializable {
	
	/** Serial ID. */
	@XmlTransient
	private static final long serialVersionUID = 1L;
	/**
	 * The number of history items that should be kept for this vehicle
	 */
	@XmlElement
	private static final int VEHICLE_HISTORY_SIZE = 4;
	/**
	 * A number representing the tolerance to error in computing equal double values. 
	 */
	@XmlElement
	private static final double ERROR_TOLERANCE = 0.01;
	/**
	 * Most recent message's time
	 */
	@XmlElement
	private double currentMessageTime;
	/**
	 * The current point that the ProjectedVehicleIds are at.
	 */
	@XmlElement
	private static int currentProjectedVehicleID = 0;
	/**
	 * The amount of time between messages at which point the vehicle will be removed
	 * from the intersection. Set by default to 30, but can be changed by the user.
	 */
	@XmlElement
	private int timeoutLength = 30;

	@XmlElement
	@XmlJavaTypeAdapter(ManagerAdapter.class)
	private HashMap<Integer, Vehicle> vehicles = new HashMap<Integer, Vehicle>();

	//~Constructors------------------------------------------------------------------------
	/**
	 * Default constructor, empty.
	 */
	public VehicleManager() {
	}
	
	@Override
	public String toString() {
		StringBuilder ret = new StringBuilder();
		
		//UtilsStringOnModel.addString(ret, UtilsLatLongConvertion.convertCalculatorType(geoCalculatorType), "calculator type");
		UtilsStringOnModel.addMap(ret, vehicles, "vehicles");
		
		return ret.toString();
	}

	public void addVehicles(List<Vehicle> vehicles) {
		this.vehicles = new HashMap<Integer, Vehicle>();
		
		for(Vehicle v:vehicles) {
			this.vehicles.put(v.getVehicleID(), v);
		}
	}
	
	public void clearVehicles() {
		vehicles = new HashMap<Integer, Vehicle>();
	}
	
	public Vehicle getVehicle(Integer vehicleId) {
		return vehicles.get(vehicleId);
	}

	/**
	 * Takes in a vehicleId and removes the vehicle denoted by that id
	 * from the vehicles map.

	 * @param vehicleId the id of the vehicle to remove.
	 */
	public void removeVehicle(int vehicleId) {
		// TODO: ablatt - is this method still necessary 
		vehicles.remove(vehicleId);
	}

	/**
	 * Takes in the max size of a set, (assuming it starts at 0) and determines
	 * if the passed check is within range of the setPosition, where range is
	 * taken from the backend, as opposed to forward.
	 * 
	 * Basically this method wraps around the end of the set to determine if
	 * the check is within range, basically making the set circular.
	 * 
	 * Returns true if the setPosition is within the range. False otherwise.
	 * 
	 * @param setSize the size of the set to check
	 * @param setPosition the position of the set
	 * @param range the range of the check
	 * @param check the number to check if its within the range of setPosition
	 * @return true if within range, false otherwise
	 */
	 boolean checkRange(int setSize, int setPosition, int range, int check) {
		// Note: ablatt - OMG its functional!!!
		boolean value;

		//if the setPosition + range wraps around to the beginning of the set, and check is less than setPosition - range
		if ((setPosition + range) % setSize < setPosition && (check < (setPosition - range))) {
			
			//if check is outside the reach of setPosition, AND check is within setPosition + range adjusted
			if (check + range < setPosition && (setPosition + range) % setSize >= check) {
				value = true;
			}
			else {
				value = false;
			}
		}
		//if the setPosition - the range wraps around to the end of the set
		else if ((setPosition - range) < 0 && check > range + setPosition) {
			int subtractor = setPosition - range;
			if (check <= setSize + subtractor) {
				value = false;
			}
			else {
				value = true;
			}
		}
		//if the setPosition - the range has no wrapping
		else {
			if (check <= setPosition - range) {
				value = false;
			}
			else {
				value = true;
			}
		}
		
		return value;
	}

	/**
	 * Get a list of vehicles in a specified lane. 
	 * 
	 * @param laneId The id of the lane to check for vehicles. 
	 * @return A list of vehicles in the specified lane ordered from closest
	 * to intersection to farthest. 
	 */
	public List<Vehicle> getVehiclesInLane(int laneId) {
		List<Vehicle> retList = new LinkedList<Vehicle>();

		for (Vehicle v : vehicles.values()) {
			if(v.getLaneID() == laneId) {
				retList.add(0, v);
			}
		}

		return retList;
	}

	/**
	 * Calculate the heading of this vehicle based on its current and previous
	 * locations, using the Java Math function atan2
	 * @param vehicle Object[] representing the history of this vehicle.
	 * @return The heading (in degrees) of this vehicle.
	 */
	private double calculateHeading(Object[] vehicle) {
		Vehicle input1 = (Vehicle) vehicle[0];
		Vehicle input2 = (Vehicle) vehicle[1];
		double vecX = input1.getX() - input2.getX();
		double vecY = input1.getY() - input2.getY();

		double angle = Math.atan2(vecY, vecX);
		double output = Math.toDegrees(angle);

		return output + 180;

	}

	public double getCurrentMessageTime() {
		return currentMessageTime;
	}

	public void setCurrentMessageTime(double currentMessageTime) {
		this.currentMessageTime = currentMessageTime;
	}

	@Override 
	public boolean equals(Object obj) {
		if(obj instanceof VehicleManager){
			VehicleManager vm = (VehicleManager) obj;
			Set<Integer> thisKeys = vehicles.keySet();
			Set<Integer> vmKeys = vm.vehicles.keySet();
			
			if(!(thisKeys.containsAll(vmKeys) && vmKeys.containsAll(thisKeys))) {
				return false;
			}
			
			for(Integer i:thisKeys) {
				if(!vehicles.get(i).equals(vm.vehicles.get(i))) {
					return false;
				}
			}
			
			return true;
		} else {
			return false;
		}
		
	}

	/**
	 * HashCode operation. 
	 * @return A hashcode of the data. 
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(77, 19).
				toHashCode();
	}
	
	public Set<Integer> getAllVehicleIds() {
		return vehicles.keySet();
	}
	
	public void addVehicle(Vehicle v) {
		vehicles.put(v.getVehicleID(), v);
	}
}
